use hypers::prelude::*;
use hypers::hyper::StatusCode;
use jsonwebtoken::{
    decode, encode, errors::ErrorKind, DecodingKey, EncodingKey, Header, Validation,
};
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime};

const SECRET_KEY: &str = "afafava-rust-lang";

#[derive(Debug, Serialize, Deserialize, Default)]
pub struct JWTClaims {
    username: String,
    exp: i64,
}

impl JWTClaims {
    pub fn generate_token(&self, secret: &str) -> Result<String, Error> {
        Ok(encode(
            &Header::default(),
            &self,
            &EncodingKey::from_secret(secret.as_ref()),
        )?)
    }

    pub fn verify(secret: &str, token: &str) -> Result<Self, Response> {
        let validation = Validation::default();
        return match decode::<JWTClaims>(
            token,
            &DecodingKey::from_secret(secret.as_ref()),
            &validation,
        ) {
            Ok(c) => Ok(c.claims),
            Err(err) => {
                let res = Response::default();
                match *err.kind() {
                ErrorKind::InvalidToken => return Err(res.render(StatusCode::UNAUTHORIZED).render("InvalidToken")),
                ErrorKind::InvalidIssuer => {
                    return Err(res.render(StatusCode::UNAUTHORIZED).render("InvalidIssuer"))
                }
                _ => return Err(res.render(StatusCode::UNAUTHORIZED).render("InvalidToken other errors")),
            }},
        };
    }

    pub fn checked_token(token: &str) -> Result<JWTClaims,Response> {
        let claims = JWTClaims::verify(SECRET_KEY, token);
        match claims {
            Ok(token) => Ok(token),
            Err(e) => Err(e),
        }
    }
}

#[hook]
async fn jwt_auth(req: Request, next: Next<'_>) -> impl Responder {
    let token = req
        .headers()
        .get("access_token")
        .map(|v| v.to_str().unwrap_or_default().to_string())
        .unwrap_or_default();
    match JWTClaims::checked_token(&token) {
        Ok(_) => Ok(next.next(req).await),
        Err(e) => Err(e),
    }
}

#[derive(Serialize, Deserialize, Default)]
pub struct ApiResponse<T> {
    code: u32,
    msg: String,
    data: T,
}

#[derive(Serialize, Deserialize,Clone,PartialEq,Debug)]
struct User {
    username: String,
    password: String,
}

// http://127.0.0.1:7878/register
async fn register(mut req: Request) -> impl Responder {
    let user = req.parse_json::<User>().await?;
    let exp = OffsetDateTime::now_utc() + Duration::days(14);
    let exp = exp.unix_timestamp();
    let jwt = JWTClaims {
        username: user.username.to_owned(),
        exp,
    };

    let token = jwt.generate_token(SECRET_KEY)?;
    Ok::<_, Error>((
        200,
        Json(ApiResponse {
            code: 200,
            msg: String::from("successful"),
            data: token,
        }),
    ))
}

async fn login(mut req: Request) -> Result<Response> {
    let user = req.parse_json::<User>().await?;
    let mut res = Response::default();
    res.status(200).json(&user);
    Ok(res)
}

#[tokio::main]
async fn main() -> Result<()> {
    let mut root = Router::default();
    root.hook(jwt_auth,vec!["/login"], vec!["/register"]);
    root.post("/register", register);
    root.post("/login", login);
    println!("root = {:#?}", root);
    let listener = hypers::TcpListener::bind("127.0.0.1:7878").await?;
    hypers::listen(root, listener).await
}

#[cfg(test)]
mod test {
    use super::*;
    use feignhttp::{post,Result};

    const BASE_URL: &str = "http://127.0.0.1:7878";

    #[post(url = BASE_URL,path = "/register")]
    async fn register(#[body] user: User) -> Result<ApiResponse<String>>{}

    #[post(url = BASE_URL,path = "/login")]
    async fn login(#[header] access_token: &str,#[body] user: User) -> Result<User>{}

    #[tokio::test]
    async fn test() -> Result<()>{
        let user = User{username: "admin".to_owned(),password: "123456".to_owned()};
        let res = register(user.clone()).await?;
        let res_user = login(&res.data, user.clone()).await?;
        assert_eq!(user,res_user);
        Ok(())
    }
}
